package shaw.wifi.server;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.SocketException;

import shaw.wifi.Constants;
import shaw.wifi.database.DatabaseConnection;
import shaw.wifi.encryption.AesEncryption;
import shaw.wifi.encryption.RsaEncryption;
import shaw.wifi.encryption.RsaServer;

public class ClientConnection implements Runnable {
	//a byte arrays holding the value of true and false (for login)
	private final String trueString = "true";
	
	private Socket socket;
	private AesEncryption aesEncryption;
	private RsaEncryption rsaEncryption;
	
	private String username;
	
	private String peapUsername;
	private String peapPassword;
	
	private ObjectOutputStream oos;

	private int clientNum = -1;
	
	private ClientConnectionManager manager;
	
	public ClientConnection(Socket s, ClientConnectionManager connectionManager) {
		socket = s;
		aesEncryption = new AesEncryption();
		rsaEncryption = new RsaEncryption();
		
		clientNum = connectionManager.register(this);
		
		manager = connectionManager;
		
		try {
			oos = new ObjectOutputStream(socket.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void run() {
		try {
			ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
			
			while(socket.isClosed() == false) {
				ShawWifiMessage msg;
				if(aesEncryption.isSetup() == true) {
					System.out.println("reading encrypted message...");
					msg = ShawWifiMessage.readEncryptedMessage(ois, aesEncryption);
				} else {
					System.out.println("reading unencrypted message...");
					msg = ShawWifiMessage.readMessage(ois);
				}
				manager.recieveMessage(this);
				System.out.println("Got message from client, handling message " + new String(msg.data[0]));
				handleMessage(msg);
			}
			System.out.println("Client socket closed!");
		} catch	(SocketException e) {
			try {
				socket.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			manager.close(this);
			ClientConnectionManager.log(System.currentTimeMillis(), "Closed connection " + clientNum);
		}
		catch (EOFException e) {
			try {
				socket.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			manager.close(this);
			ClientConnectionManager.log(System.currentTimeMillis(), "Closed connection " + clientNum);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void handleMessage(ShawWifiMessage msg) {
		if(msg.type == ShawWifiMessage.TYPE_AUTH) {
			handleAuthMsg(msg);
		} else if(msg.type == ShawWifiMessage.TYPE_NETWORKSETTINGS) {
			handleNsMsg(msg);
		} else if(msg.type == ShawWifiMessage.TYPE_ESTABLISHCONNECTION) {
			handleSetupConMsg(msg);
		} else if(msg.type == ShawWifiMessage.TYPE_GETAPPCRED) {
			handleGetAppCredMsg(msg);
		}
	}
	
	private void sendMessage(ShawWifiMessage msg) {
		try {
			msg.sendMessage(oos);
			System.out.println("Send message " + msg.header[0]);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void sendCryptMessage(ShawWifiMessage msg) {
		try {
			msg.sendEncryptedMessage(oos, aesEncryption);
			System.out.println("Send message " + msg.header[0]);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void sendErrorMessage(String errStr, ShawWifiMessage msg) {
		ShawWifiMessage err = new ShawWifiMessage(1);
		err.type = ShawWifiMessage.TYPE_ERROR;
		err.header[0] = "ERROR";
		err.data[0] = errStr.getBytes();
		System.out.println("Sent error to client " + errStr);
		
		if(aesEncryption.isSetup()) {
			sendCryptMessage(msg);
		} else {
			sendMessage(msg);
		}
	}

	private void handleNsMsg(ShawWifiMessage msg) {
		System.out.println("handling message " + msg.header[0]);
		if(!msg.header[0].equals("action")) {
			sendErrorMessage("Expected an action", msg);
			return;
		}
		if(binIsStr(msg.data[0], "getpeap")) {
			System.out.println("getting peap...");
			if(msg.length != 3) {
				sendErrorMessage("Invalid number of parameters for message", msg);
			}
			if(!msg.header[1].equals("username") || !msg.header[2].equals("password")) {
				sendErrorMessage("Unexpected headers for message", msg);
			}
			
			username = new String(msg.data[1]);
			String password = new String(msg.data[2]);
			System.out.println("got username " + username + " : " + password);
			
			if(!DatabaseConnection.get().verifyUser(username, password)) {
				System.out.println("invalid username/password");
				ShawWifiMessage response = new ShawWifiMessage(1);
				response.type = ShawWifiMessage.TYPE_NETWORKSETTINGS;
				response.header[0] = "LOGIN";
				response.data[0] = "false".getBytes();
				
				sendCryptMessage(response);
				return;
			}
			
			ShawWifiMessage response = new ShawWifiMessage(5);
			
			//create suggested username/password strings
			String randomStr = DatabaseConnection.randomString();
			peapUsername = randomStr.substring(0, 15);
			peapPassword = randomStr.substring(16, 31);
			
			response.type = ShawWifiMessage.TYPE_NETWORKSETTINGS;
			response.header[0] = "LOGIN";
			response.data[0] = trueString.getBytes();
			response.header[1] = "NETWORK";
			response.data[1] = Constants._SEC_SSID.getBytes();
			response.header[2] = "OPENNETWORK";
			response.data[2] = Constants._OPEN_SSID.getBytes();
			response.header[3] = "USERNAME";
			response.data[3] = peapUsername.getBytes();
			response.header[4] = "PASSWORD";
			response.data[4] = peapPassword.getBytes();
			
			sendCryptMessage(response);
			System.out.println("Sent LOGIN good " + response.header[0] + " " + new String(response.data[0]));
		}
		if(binIsStr(msg.data[0], "setpeap")) {
			if(msg.length != 3) {
				sendErrorMessage("Invalid number of parameters for message", msg);
			}
			if(!msg.header[1].equals("crad_user") || !msg.header[2].equals("crad_pass")) {
				sendErrorMessage("Unexpected headers for message", msg);
			}
			
			String peapUser = new String(msg.data[1]);
			String peapPass = new String(msg.data[2]);
			if(!peapUsername.equals(peapUser) || !peapPassword.equals(peapPass)) {
				this.sendErrorMessage("Unrecognized peap username/password", msg);
				return;
			}
			DatabaseConnection.get().insertRadiusUsernamePasswordForUser(peapUser, peapPass, username);
			
			ShawWifiMessage response = new ShawWifiMessage(1);
			response.type = ShawWifiMessage.TYPE_NETWORKSETTINGS;
			response.header[0] = "OK";
			response.data[0] = trueString.getBytes();
			
			sendCryptMessage(response);
		}
	}

	private void handleSetupConMsg(ShawWifiMessage msg) {
		if(binIsStr(msg.data[0], "requestkey")) {
			if(msg.length != 1) {
				sendErrorMessage("Invalid number of parameters for message", msg);
			}
			ShawWifiMessage response = new ShawWifiMessage(2);
			response.type = ShawWifiMessage.TYPE_ESTABLISHCONNECTION;
			response.length = 2;
			
			response.header[0] = "RSAEXP";
			response.data[0] = rsaEncryption.getExp();
			
			response.header[1] = "RSAMOD";
			response.data[1] = rsaEncryption.getMod();
			
			sendMessage(response);
		}
		if(binIsStr(msg.data[0], "setsecret")) {
			if(msg.length != 3) {
				sendErrorMessage("Invalid number of parameters for message" , msg);
			}
			
			if(!msg.header[1].equals("sharedkey") || !msg.header[2].equals("sharediv")) {
				sendErrorMessage("Invalid message headers", msg);
			}
			
			byte[] cryptSecret = msg.data[1];
			byte[] cryptIv = msg.data[2];
			
			byte[] plainSecret = rsaEncryption.decryptRsa(cryptSecret);
			byte[] plainIv = rsaEncryption.decryptRsa(cryptIv);
			
			aesEncryption.setSharedSecret(plainSecret);
			aesEncryption.setIvParameterSpec(plainIv);
			
			ShawWifiMessage response = new ShawWifiMessage(1);
			response.type = ShawWifiMessage.TYPE_ESTABLISHCONNECTION;
			response.header[0] = "OK";
			response.data[0] = trueString.getBytes();
			
			sendCryptMessage(response);
		}
	}

	private void handleAuthMsg(ShawWifiMessage msg) {
		if(binIsStr(msg.data[0], "authenticate")) {
			if(msg.length != 2) {
				sendErrorMessage("Invalid number of parameters for message" , msg);
			}
			
			if(!msg.header[1].equals("challenge")) {
				sendErrorMessage("Invalid message headers", msg);
			}
			
			byte[] cryptChallenge = msg.data[1];
			byte[] plainChallenge = RsaServer.get().decryptRsa(cryptChallenge);
			
			System.out.println("Authenticating with value " + new String(plainChallenge));
			
			ShawWifiMessage response = new ShawWifiMessage(1);
			response.type = ShawWifiMessage.TYPE_AUTH;
			response.header[0] = "RESULT";
			response.data[0] = plainChallenge;
			
			sendMessage(response);
		}
	}
	
	private void handleGetAppCredMsg(ShawWifiMessage msg) {
		if(binIsStr(msg.data[0], "getappcred")) {
			if(msg.length != 1) {
				sendErrorMessage("Invalid number of parameters for message" , msg);
			}
			
			String[] unpwd = DatabaseConnection.get().generateUsernamePassword();
			String username = unpwd[0];
			String password = unpwd[1];
			
			ShawWifiMessage response = new ShawWifiMessage(2);
			response.header[0] = "USERNAME";
			response.data[0] = username.getBytes();
			response.header[1] = "PASSWORD";
			response.data[1] = password.getBytes();
			
			sendCryptMessage(response);
			
			return;
		}
		
		sendErrorMessage("Unrecognized message", msg);
	}

	public void timeout() {
		try {
			socket.close();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		manager.close(this);
		ClientConnectionManager.log(System.currentTimeMillis(), "Closed connection " + clientNum + " (timeout)");		
	}

	private boolean binIsStr(byte[] inBytes, String string) {
		byte[] strBytes = string.getBytes();
		
		if(strBytes.length != inBytes.length) {
			return false;
		}
		for(int i = 0; i<inBytes.length; i++) {
			if(inBytes[i] != strBytes[i]) {
				return false;
			}
		}
		return true;
	}
}
